跳转至

仓库中的问题追踪:探索去中心化基于 Git 的问题追踪系统

文章背景与核心概要

当 GitHub 等中心化代码托管服务发生故障时,开发者虽然可以通过本地克隆继续访问代码,但存储在专有数据库中的问题追踪器(Issue Trackers)和合并请求(Pull Requests)却会陷入瘫痪。这种脆弱性促使开发者进行了长达二十年的实验,试图将问题和代码审查数据直接保留在 Git 仓库内部,从而使其能够随着源码一起进行克隆、推送和保持同步。本文深入探讨了用于实现这一目标的五种主要数据模型,评估了它们在存储、合并、身份识别以及传输方面的具体表现。


1. Files in the Working Tree

1. 工作树中的文件

The oldest approach makes each issue a text file or directory checked in right alongside source code. * Historical Examples: Bugs Everywhere (2005) and ditz (YAML from 2008). * Modern Takes: GitRoot builds a self-hosted forge keeping issues as issues/<id>-<slug>.md with YAML front-matter; Matthew Martin uses a lightweight ticket directory reusing keep-a-changelog fragments. Diomidis Spinellis’s git-issue manages a nested .issues/ git repository inside the working tree.

最早的方法是将每个问题作为一个文本文件或目录,直接与源代码一起检入。 * 历史案例: Bugs Everywhere (2005) 和 ditz (2008年的 YAML)。 * 现代实践: GitRoot 构建了一个自托管的代码托管平台,将问题保存为带有 YAML 前置表达式的 issues/<id>-<slug>.md;Matthew Martin 使用了一个复用 keep-a-changelog 片段的轻量级 ticket directory。Diomidis Spinellis 的 git-issue 则在工作树内部管理一个嵌套的 .issues/ git 仓库。

Characteristics

特性

  • Pros: Fully compatible with standard Git commands (git clone, grep -r, cat, git bundle). Concurrent edits undergo standard three-way text merging. Identity relies directly on Git authors and commit signatures via git blame.
  • Cons: Issue history and code history share a single commit graph, cluttering git log and causing issues when branching (e.g., an issue closed on a feature branch reappears as open when switching back to main).
  • 优点: 与标准 Git 命令完全兼容(git clonegrep -rcatgit bundle)。并发编辑会经历标准的三方文本合并。身份识别直接依赖于 Git 作者以及通过 git blame 获取的提交签名。
  • 缺点: 问题历史记录与代码历史记录共享单个提交图,导致 git log 变得杂乱,并且在分支切换时会引发问题(例如,在特性分支上关闭的问题在切回 main 分支时会重新显示为打开状态)。

2. Orphan Branches

2. 孤立分支(Orphan Branches)

An orphan branch is a ref under refs/heads/ whose root commit has no parent, keeping its files completely separate from main and invisible to normal checkouts. * Examples: Scott Chacon’s ticgit (2008), its modern fork ticgit-ng, and the haxy forge, which uses an event log on refs/heads/haxy/events.

孤立分支是指 refs/heads/ 下的一个引用,其根提交没有父提交,这使得其文件与 main 完全隔离,且对常规检出(checkout)不可见。 * 示例: Scott Chacon 的 ticgit (2008)、其现代分支 ticgit-ng,以及使用 refs/heads/haxy/events 上的事件日志的 haxy 代码托管平台。

Characteristics

特性

  • Pros: git log on main stays free of issue noise, and a default git clone automatically fetches the orphan branch. Ticgit encodes state into filenames (e.g., STATE_open, ASSIGNED_<email>), letting git ls-tree display issue states without reading blobs.
  • Cons: Shows up in git branch -a, making it vulnerable to accidental deletion via git push --prune or routine repo cleanups. Furthermore, stock bare hosts lack server-side reflogs to recover a deleted orphan graph.
  • 优点: main 上的 git log 保持干净、没有问题噪音,并且默认的 git clone 会自动获取孤立分支。Ticgit 将状态编码到文件名中(例如 STATE_openASSIGNED_<email>),允许 git ls-tree 在不读取 blob 的情况下显示问题状态。
  • 缺点: 会显示在 git branch -a 中,容易通过 git push --prune 或常规仓库清理被意外删除。此外,标准的裸仓库(bare hosts)缺乏服务器端 reflog 来恢复已删除的孤立图谱。

3. Git Notes

3. Git Notes(Git 注释)

Git features a built-in notes facility that attaches arbitrary blobs to existing commits without rewriting them, stored under refs/notes/<namespace>. * Example: Google’s git-appraise, which built distributed code review entirely on notes.

$ git for-each-ref refs/notes/
e70fe480... commit  refs/notes/review

$ git cat-file -p refs/notes/review^{tree}
100644 blob 677b652c...  0a72c46f9a191c6097f708ccdabcd6af13eebede

$ git cat-file -p 677b652c
{"timestamp":"2026-08-18T00:00:00Z","reviewRef":"refs/heads/main",
 "targetRef":"refs/heads/master","description":"Demo review request",
 "requester":"andrew@example.com"}

Git 具有内置的 notes 功能,它可以在不重写现有提交的情况下将任意 blob 附加到现有提交上,并存储在 refs/notes/<namespace> 下。 * 示例: Google 的 git-appraise,它完全基于 notes 构建了分布式代码审查。

$ git for-each-ref refs/notes/
e70fe480... commit  refs/notes/review

$ git cat-file -p refs/notes/review^{tree}
100644 blob 677b652c...  0a72c46f9a191c6097f708ccdabcd6af13eebede

$ git cat-file -p 677b652c
{"timestamp":"2026-08-18T00:00:00Z","reviewRef":"refs/heads/main",
 "targetRef":"refs/heads/master","description":"Demo review request",
 "requester":"andrew@example.com"}

Characteristics

特性

  • Pros: Native conflict-resolution strategies like git notes merge (using cat_sort_uniq) concatenate and deduplicate conflicting notes line-by-line. Notes are universally accepted by standard git hosts.
  • Cons: Notes must hang off existing commits. This model suits code review and CI results exceptionally well, but fits general bug reports less naturally.
  • 优点: 原生冲突解决策略(如使用 cat_sort_uniqgit notes merge)可以逐行连接和去重冲突的 notes。Notes 被标准的 git 托管服务普遍接受。
  • 缺点: Notes 必须依附于现有的提交。该模型非常适合代码审查和 CI 结果,但对通用缺陷报告(bug reports)的契合度不够自然。

4. Custom Ref Namespaces

4. 自定义引用命名空间

Currently the most active approach, storing data under ref namespaces outside refs/heads/ so nothing appears in git branch and the working tree remains untouched. * Examples: git-bug (uses Lamport clocks and commit chains under refs/bugs/<id>), Gerrit’s NoteDb, Radicle, and git-native-issue.

$ git fetch origin 'refs/bugs/*:refs/bugs/*' 'refs/identities/*:refs/identities/*'
$ git for-each-ref refs/bugs/ | head -1
aecc49e1... commit  refs/bugs/0066216d1e0cc97ad1c9eaa553459ca85a0d57e133e...

$ git cat-file -p aecc49e1^{tree}
100644 blob e69de29b...  create-clock-354
100644 blob e69de29b...  edit-clock-2314
100644 blob 588a2a80...  ops
100644 blob e69de29b...  version-4

$ git cat-file -p 588a2a80
{"author":{"id":"193531e4590156..."},
 "ops":[{"type":1,"timestamp":1722433238,
         "title":"Issue running the example_test.go",
         "message":"Hi y'all, I'm really excited...","files":null}]}

这是目前最活跃的方法,将数据存储在 refs/heads/ 之外的引用命名空间中,这样 git branch 中就不会显示任何内容,并且工作树保持不被修改。 * 示例: git-bug(在 refs/bugs/<id> 下使用兰伯特时钟和提交链)、Gerrit 的 NoteDbRadiclegit-native-issue

$ git fetch origin 'refs/bugs/*:refs/bugs/*' 'refs/identities/*:refs/identities/*'
$ git for-each-ref refs/bugs/ | head -1
aecc49e1... commit  refs/bugs/0066216d1e0cc97ad1c9eaa553459ca85a0d57e133e...

$ git cat-file -p aecc49e1^{tree}
100644 blob e69de29b...  create-clock-354
100644 blob e69de29b...  edit-clock-2314
100644 blob 588a2a80...  ops
100644 blob e69de29b...  version-4

$ git cat-file -p 588a2a80
{"author":{"id":"193531e4590156..."},
 "ops":[{"type":1,"timestamp":1722433238,
         "title":"Issue running the example_test.go",
         "message":"Hi y'all, I'm really excited...","files":null}]}

Characteristics

特性

  • Pros: Bypasses text-level conflicts entirely; offline edits diverge into separate commit chains that tools automatically merge by replaying operation logs against Lamport clocks. Bridges can sync these refs to GitHub, GitLab, or Jira.
  • Cons: Skipped by default clones (requiring explicit refspec configuration) and largely unreadable without specialized tools.
  • 优点: 完全避开了文本级别的冲突;离线编辑会分叉成独立的提交链,工具可以通过针对兰伯特时钟重放操作日志来自动合并它们。桥接工具还可以将这些引用同步到 GitHub、GitLab 或 Jira。
  • 缺点: 默认克隆会跳过这些引用(需要显式的 refspec 配置),并且在没有专业工具的情况下几乎无法直接阅读。

5. Built Into the VCS (Fossil)

5. 内置于 VCS 中 (Fossil)

Fossil was designed from the ground up to bundle tickets, wikis, forum threads, and source code into a single content-addressed SQLite database file per repository.

$ fossil artifact 94cd0c6265
D 2026-08-18T06:37:37.865
J comment Something\sis\sbroken
J priority Medium
J status Open
J title Demo\sbug
J type Code_Defect
K 821e9e51da937dc2c37227ef8ed57c757e7530f0
U andrew
Z 1c440428fff5e32c7c5304e9f6cd5bec

Fossil 从底层开始设计时,就将工单、维基、论坛主题和源代码打包到每个仓库单个基于内容寻址的 SQLite 数据库文件中。

$ fossil artifact 94cd0c6265
D 2026-08-18T06:37:37.865
J comment Something\sis\sbroken
J priority Medium
J status Open
J title Demo\sbug
J type Code_Defect
K 821e9e51da937dc2c37227ef8ed57c757e7530f0
U andrew
Z 1c440428fff5e32c7c5304e9f6cd5bec

Characteristics

特性

  • Pros: Everything travels seamlessly with a single fossil sync or fossil clone. Concurrent edits survive as individual state-changing artifacts, implementing a last-writer-wins per-field merge policy.
  • Cons: Completely separate from Git, requiring explicit import/export bridges to interoperate with Git-based workflows.
  • 优点: 所有内容都可以通过单一的 fossil syncfossil clone 无缝传输。并发编辑作为独立的状态变更构件(artifacts)得以保存,并实现了每个字段“最后写入者胜出(last-writer-wins)”的合并策略。
  • 缺点: 与 Git 完全隔离,需要显式的导入/导出桥接才能与基于 Git 的工作流进行互操作。

Backup and Transport: git bundle

备份与传输:git bundle

For Git-based implementations, running git bundle create backup.bundle --all (after fetching custom refs or notes namespaces) packs all issue tracking refs, metadata, identities, and code into a single standalone file. Whether using working tree files, orphan branches, notes, or custom refs, a single bundle file can reliably preserve code and issue trackers together for offline storage or emergency backups.

对于基于 Git 的实现,运行 git bundle create backup.bundle --all(在获取自定义引用或 notes 命名空间后)可以将所有问题追踪引用、元数据、身份信息以及代码打包到一个单独的独立文件中。无论使用的是工作树文件、孤立分支、notes 还是自定义引用,单个 bundle 文件都可以可靠地将代码和问题追踪器一同保存,用于离线存储或紧急备份。